
package com.urbansquall.ginger
{
	import com.urbansquall.ginger.events.AnimationEvent;
	import com.urbansquall.ginger.events.FrameEvent;
	
	import flash.display.Bitmap;
	import flash.display.BitmapData;
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.TimerEvent;
	import flash.utils.Dictionary;
	import flash.utils.Timer;

/**
 * 
 * @author Colbycheeze, Panayoti
 * 
 */
public class AnimationPlayer extends Sprite
{
	
	public override function set rotation( value:Number ):void
 	{
 		m_rotation = int( value );
		
 		//Make sure value is between 0 and 360
 		m_rotation %= 360;
 		if( m_rotation < 0)
 		{
 			m_rotation += 360;
 		}

 		m_rotationFrame = Math.round( m_rotation / ( m_currentAnimation.m_degToRotBy ) );
		
 		//If we are at the 360 frame, set back down to 0 to
 		//eliminate errors
 		if( m_rotationFrame >= m_currentAnimation.m_framesOfRotation )
 		{
 			m_rotationFrame = 0;
 		}
 		
 		performOffset();

 		m_hasRotationChanged = true;
 	}
	
	public override function get rotation():Number { return m_rotation; }
	public override function set x(value:Number) : void
	{
		m_desiredX = value;
		// super.x = value - offsetX;
		performOffset();
	}	
	public override function get x() : Number
	{
		return m_desiredX;
	}
	
	public override function set y(value:Number) : void
	{
		m_desiredY = value;
		performOffset();
		// super.y = value - offsetY;
	}	
	public override function get y() : Number
	{
		return m_desiredY;
	}
	override public function set scaleX( a_value : Number ) : void
	{
		super.scaleX = a_value;
		
		performOffset();
	}
	
	override public function set scaleY( a_value : Number ) : void
	{
		super.scaleY = a_value;
		
		performOffset();
	}
	
	/**
	 * Set the frame rate of the animation. 
	 * @param value frame rate per second
	 * 
	 */	
	public function set fps( value:int ) : void
	{
		m_fps = value;
		
 		if( value <= 0 )
 		{
 			m_fps = 0;
 			m_animationDelay = 0;
 		}
 		else
 		{
 			m_fps = value;
 			m_animationDelay = 1000 / value;
 		}
	}	
	public function get fps() : int { return m_fps; }
	
	/**
	 *  
	 * @return The ID of the current animation that plays by default once all other animations are finished.
	 * 
	 */	
	public function get defaultAnimationID() : String { return m_defaultAnimationID; }
	/**
	 * Set the default animation to this ID. In other words, the animation that plays once all others are complete.
	 * @param value The ID of the animation.
	 * 
	 */	
	public function set defaultAnimationID( value:String ) : void { m_defaultAnimationID = value; }
	
	/**
	 * 
	 * @return The ID of the current animation that is playing
	 * 
	 */	
	public function get currentAnimationID() : String { return m_currentAnimationID; }
	public function get currentFrameID() : String
	{
		return m_currentFrame.frameID;
	}
	
	/**
	 * 
	 * @return An array list of all ID's associated with this player
	 * 
	 */	
	public function get animationIDs() : Array { return m_IDArray.slice(); }
	
	/**
	 * 
	 * @return The current frame's offset from ( 0, 0 ) 
	 * 
	 */	
	public function get offsetX(): Number
	{
		return m_currentFrame.offsetX[ m_rotationFrame ];
	}	
	/**
	 * 
	 * @return The current frame's offset from ( 0, 0 ) 
	 * 
	 */	
	public function get offsetY(): Number
	{
		return m_currentFrame.offsetY[ m_rotationFrame ];
	}
	
	public override function get width():Number { return m_currentFrame.width[ m_rotationFrame ]; }
	public override function set width(value:Number):void { throw new Error( "<AnimationPlayer> Width / Height cannot be changed!" ); }
	public override function get height():Number { return m_currentFrame.height[ m_rotationFrame ]; }
	public override function set height(value:Number):void { throw new Error( "<AnimationPlayer> Width / Height cannot be changed!" ); }
		
	public function AnimationPlayer()
	{
		m_IDArray = [];
		m_animationQueue = [];
		m_frames = [];
		m_animationMap = new Dictionary();
		m_framesMap = new Dictionary();
	
		m_bitmap = new Bitmap( null, "never", false  );
		
		addChild( m_bitmap );
	}
	
	/**
	 * Add a new animation to this player. If it is the first animation, it will be set to the default
	 * @param a_animationID The ID to reference this animation by
	 * @param a_animation The instance of the animation to be used
	 * 
	 */	
	public function addAnimation( a_animationID : String, a_animation : Animation ) : void
	{
		m_IDArray.push( a_animationID );
		m_animationMap[ a_animationID ] = a_animation;
		if( m_defaultAnimationID == null )
		{
			m_defaultAnimationID = a_animationID;

			play( a_animationID );
			
			performOffset();
		}
	}
	
	/**
	 * switches playhead to this animation immediately
	 * @param a_animationID The ID of the animation to switch to
	 *
	 */
	public function play( a_animationID : String ) : void
	{
		if( m_animationMap[ a_animationID ] == null )
		{
			throw new Error( "<AnimationPlayer> Invalid Animation ID [" + a_animationID + "]" );
		}
		
		m_isPlaying = true;
		m_hasAnimationEnded = false;

		m_animationQueue = [ m_currentAnimation ];
		
		startNewAnimation( a_animationID );
	}
	
	/**
	 * Jumps to a specific frame of animation 
	 * @param a_frameID The ID of the frame to jump to
	 * @param a_animationID The ID of the animation that the frame is located in. If left as null, the current animation will be used
	 * 
	 */	
	public function gotoFrame( a_frameID : String, a_animationID : String = null ) : void
	{
		if( a_animationID != null )
		{
			startNewAnimation( a_animationID );
		}
		
		var index:int = m_framesMap[ a_frameID ];
				
		m_currentFrame = Frame( m_frames[ index ] );
		
		performOffset();

		m_bitmap.bitmapData = m_currentFrame.bitmapData[ m_rotationFrame ] as BitmapData;
		m_frameIndex = index;
		m_framesPassed = 0;
		m_isPlaying = false;
		
		if( m_currentFrame.frameID != null )
		{
			if( hasEventListener( FrameEvent.CHANGE ) )
			{
				dispatchEvent( new FrameEvent( FrameEvent.CHANGE, true, m_currentFrame.frameID, m_currentAnimationID ) );
			}
		}
	}
	
	
	public function isPaused() : Boolean { return m_isPlaying; }
	public function unpause() : void { if( m_currentAnimation != null ){ m_isPlaying = true; } }
	public function pause() : void { m_isPlaying = false; }
	public function togglePause() : void { if( m_currentAnimation != null ){ m_isPlaying = !m_isPlaying; } }

	/**
	 * Use this function to have the AnimationPlayer update itself. The AnimationPlayer MUST be added to the stage. Updating will
	 * stop once it is removed from the stage.
	 * @param a_interval How often update is to be called ( in ms )
	 * 
	 */	
	public function startSelfTick( a_interval : int ):void
	{
		if( a_interval <= 0 ) return;
		
		stopSelfTick();
		
		m_timerInterval = a_interval;
		m_timer = new Timer( m_timerInterval );
		m_timer.addEventListener( TimerEvent.TIMER, tickTimer, false, 0, true );
		m_timer.start();
		
		this.addEventListener( Event.REMOVED_FROM_STAGE, handleRemovedFromStage, false, 0, true );
	}
	
	/**
	 * Cancel self updating 
	 * 
	 */	
	public function stopSelfTick() : void
	{
		if( m_timer != null )
		{
			this.removeEventListener( Event.REMOVED_FROM_STAGE, handleRemovedFromStage, false );
		
			m_timer.removeEventListener( TimerEvent.TIMER, tickTimer, false );
			m_timer.stop();
			m_timer = null;
		}
	}
	
	/**
	 * Updates the AnimationPlayer. Must be called each game tick manually, unless StartSelfTick is set. 
	 * @param a_ms The amount of time passed since the last update ( in ms )
	 * 
	 */	
	public function update( a_ms : Number ) : void
	{
 		if( m_hasAnimationEnded )
		{
			if( hasEventListener( AnimationEvent.CHANGE ) )
			{
				dispatchEvent( new AnimationEvent( AnimationEvent.CHANGE, false, m_currentAnimationID ));
			}
						
			if( m_isLooping )
			{
				startNewAnimation( m_animationQueue[ 0 ] );
			}
			else if( m_animationQueue.length <= 1 )
			{
				m_animationQueue.splice( 0 );
				
				if( m_currentAnimationID == m_defaultAnimationID )
				{
					m_isPlaying = false;
				}
				else
				{
					startNewAnimation( m_defaultAnimationID );
				}
			}
			else
			{
				m_animationQueue.shift();
				startNewAnimation( m_animationQueue[ 0 ] );
			}
			
			m_hasAnimationEnded = false;
		}
		else if( m_hasRotationChanged )
		{
			m_bitmap.bitmapData = m_currentFrame.bitmapData[ m_rotationFrame ];
			m_hasRotationChanged = false;
		}
		
		if(m_isPlaying)
 		{
 			m_timeSinceLastUpdate += a_ms;
 			if( m_timeSinceLastUpdate > m_animationDelay )
 			{
 			 	m_timeSinceLastUpdate -= m_animationDelay;

 				updateFrame();
 			}
 		}
	}
	
	/**
	 * adds the id to an animation queue
     * when the active animation completes
     * the next animation in the queue is begun
	 * @param a_animationID
	 *
	 */
	public function queue( a_animationID : String ) : void
	{
		var animation:Animation = m_animationMap[ a_animationID ];
		if( animation == null )
		{
			throw new Error( "<AnimationPlayer> Invalid Animation ID!" );
		}
		m_animationQueue.push( a_animationID );
	}
	
	/**
	 * Copies this animation player for reuse 
	 * @return A duplicate version of this AnimationPlayer
	 * 
	 */	
	public function copy() : AnimationPlayer
	{
		var player:AnimationPlayer = new AnimationPlayer();
		for( var i:int = 0; i < m_IDArray.length; i++ )
		{
			var id:String = m_IDArray[ i ];
			player.addAnimation( id, m_animationMap[ id ] );
		}
		
		player.defaultAnimationID = m_defaultAnimationID;
		player.fps = m_fps;
		player.play( m_defaultAnimationID );
		player.m_bitmap.bitmapData = m_bitmap.bitmapData;
		
		return player;
	}
	
	
	// -- PRIVATE --
	
	private function handleRemovedFromStage( a_event : Event ) : void
	{
		stopSelfTick();
	}
	
	private function tickTimer( a_event : TimerEvent ):void
	{
		update( m_timerInterval );
	}
	
	private function performOffset() : void
	{
		var signX : int = -1;
		var signY : int = -1;
		
		if( scaleX < 0 ) { signX = 1; }
		if( scaleY < 0 ) { signY = 1; }
		
		super.x = m_desiredX + signX * offsetX;
		super.y = m_desiredY + signY * offsetY;
	}
	
	private function startNewAnimation( a_animationID:String ) : void
	{
		m_timeSinceLastUpdate = 0;
		m_currentAnimationID = a_animationID;
		m_currentAnimation = m_animationMap[ m_currentAnimationID ];

		if( m_currentAnimation == null )
		{
			m_currentAnimationID = m_defaultAnimationID;
			m_currentAnimation = m_animationMap[ m_currentAnimationID ];
		}
		
		fps = m_currentAnimation.m_fps;
		m_frames = m_currentAnimation.m_frames;
		m_framesMap = m_currentAnimation.m_frameMap;
		m_isLooping = m_currentAnimation.isLooping;
		
		m_totalFrames = m_frames.length;
		m_frameIndex = 0;
		m_currentFrame = Frame( m_frames[0] );
		
		m_bitmap.bitmapData = m_currentFrame.bitmapData[ m_rotationFrame ];
		
		performOffset();
		
		if( hasEventListener( AnimationEvent.CHANGE ) )
		{
			dispatchEvent( new AnimationEvent( AnimationEvent.CHANGE, true, m_currentAnimationID ) );
		}
	}
	
	private function updateFrame() : void
	{
		if( m_currentFrame == null ) { return; }

		m_framesPassed++;
		if( m_framesPassed > m_currentFrame.numFrames )
		{
			m_framesPassed = 0;
			if( ++m_frameIndex >= m_totalFrames )
			{
				if( !m_isLooping )
				{
					m_hasAnimationEnded = true;
					return;
				}
				else
				{
					m_frameIndex = 0;
				}
			}
			
			m_currentFrame = Frame( m_frames[ m_frameIndex ] );
			
			m_bitmap.bitmapData = m_currentFrame.bitmapData[ m_rotationFrame ];
			
			performOffset();
			
			if( m_currentFrame.frameID != null )
			{
				if( hasEventListener( FrameEvent.CHANGE ) )
				{
					dispatchEvent( new FrameEvent( FrameEvent.CHANGE, true, m_currentFrame.frameID, m_currentAnimationID ) );
				}
			}
		}
	}
	
	private var m_framesPassed:int;
	private var m_totalFrames:int;
	private var m_currentFrame:Frame;
	private var m_frameIndex:int;
	private var m_frames:Array;
	private var m_framesMap:Dictionary;
	private var m_isLooping:Boolean;

	private var m_rotationFrame:int;
    private var m_hasRotationChanged:Boolean;
    private var m_rotation:Number=0;
    private var m_IDArray:Array; //used for copy function
    private var m_hasAnimationEnded:Boolean;
    private var m_animationDelay:int;
    private var m_timeSinceLastUpdate:int;
    private var m_currentAnimation:Animation;
    private var m_isPlaying:Boolean;
    private var m_animationMap:Dictionary;
    private var m_animationQueue:Array;
    private var m_currentAnimationID:String;
    private var m_defaultAnimationID:String;
    private var m_fps:int;
	
	private var m_desiredX : Number = 0;
	private var m_desiredY : Number = 0;
	
	private var m_bitmap : Bitmap;
	private var m_timer : Timer;
	private var m_timerInterval : int = 0;
}
}
	